package com.gitee.wsl.ext.base

import com.gitee.wsl.ext.number.takeIfNotZero
import com.gitee.wsl.ext.string.quoted
import com.gitee.wsl.ext.string.splitAndTrimToSet
import kotlin.enums.EnumEntries
import kotlin.enums.enumEntries
import kotlin.reflect.KProperty1


fun <T : Enum<T>> findSet(values: EnumEntries<T>, indexes: Set<String>, defaultValue: T): Set<T> {
    val results: Set<T> = indexes.map { findOne(values, it.toInt(), defaultValue) }.toSet()
    return results.ifEmpty { values.toSet() }
}

fun <T : Enum<T>> findOne(values: EnumEntries<T>, index: Int, defaultValue: T): T =
    if (index in values.indices) values[index] else defaultValue


fun <T : Enum<T>> EnumEntries<T>.ordinals( ): Set<String> = toSet().ordinals()


fun <T : Enum<T>>  Set<T>.ordinals(): Set<String> = map { it.ordinal.toString() }.toSet()


/**
 * @return whether the enum exists and is one of the [enums]
 */
fun <E : Enum<E>> E?.isOneOf(vararg enums: E): Boolean
{
    this ?: return false
    return enums.contains(this)
}

/**
 * @return the enum value of the string
 */
inline fun <reified E : Enum<E>> String.enumValue(): E = enumValueOf(this)

/**
 * @return the enum value of the string, or null if it is invalid
 */
inline fun <reified E : Enum<E>> String.enumValueOrNull(): E? = try
{
    enumValueOf<E>(this)
} catch (e: Exception)
{
    null
}

/**
 * @return the enum values of the collection of strings that can be converted
 */
inline fun <reified E : Enum<E>> Collection<String?>.validEnumValues(): List<E> =
    mapNotNull { s -> s?.enumValueOrNull<E>() }

/**
 * @return the enum values of the map of strings that can be converted
 */
@Suppress("UNCHECKED_CAST")
inline fun <reified E : Enum<E>, T> Map<String, T>.validEnumValues(): Map<E, T> =
    mapKeys { e -> e.key.enumValueOrNull<E>() }.minus(null) as Map<E, T>


inline operator fun <reified E : Enum<E>> E.plus(other: Int): E =
    enumEntries<E>()[ordinal + other]

inline operator fun <reified E : Enum<E>> E.minus(other: Int) = this + -other
inline operator fun <reified E : Enum<E>> E.inc() = this + 1
inline operator fun <reified E : Enum<E>> E.dec() = this - 1


/**
 * Returns the next value of this enum (ordered the same way as declared in code, i.e. by `ordinal`).
 *
 * If the current value is the last, returns the first value.
 * @see nextOrNull
 * @see previousOrNull
 */
@ExperimentalStdlibApi
inline val <reified T : Enum<T>> Enum<T>.next: T get() = enumEntries<T>().let { it[(ordinal + 1) % it.size] }

/**
 * Returns the next value of this enum (ordered the same way as declared in code, i.e. by `ordinal`).
 *
 * If the current value is the last, returns `null`.
 * @see next
 * @see previous
 */
@ExperimentalStdlibApi
inline val <reified T : Enum<T>> Enum<T>.nextOrNull: T? get() = enumEntries<T>().getOrNull(ordinal + 1)

/**
 * Returns the previous value of this enum (ordered the same way as declared in code, i.e. by `ordinal`).
 *
 * If the current value is the first, returns the last value.
 * @see previousOrNull
 * @see next
 */
@ExperimentalStdlibApi
 inline val <reified T : Enum<T>> Enum<T>.previous: T
    get() = enumEntries<T>().let { it[(ordinal.takeIfNotZero() ?: it.size) - 1] }

/**
 * Returns the previous value of this enum (ordered the same way as declared in code, i.e. by `ordinal`).
 *
 * If the current value is the first, returns `null`.
 * @see next
 * @see previous
 */
@ExperimentalStdlibApi
inline val <reified T : Enum<T>> Enum<T>.previousOrNull: T? get() = enumEntries<T>().getOrNull(ordinal - 1)

/**
 * Returns the enum entry [stepSize] after this enum entry, in declaration order.
 * Cycles back to the first entry if the end is reached.
 */
inline fun <reified E : Enum<E>> E.next(stepSize: Int = 1): E {
    val entries = enumEntries<E>()
    return entries[(ordinal + stepSize).mod(entries.size)]
}

/**
 * Returns the enum entry [stepSize] after this enum entry, in declaration order.
 * Returns `null` if the end is reached.
 */
inline fun <reified E : Enum<E>> E.nextOrNull(stepSize: Int = 1): E? {
    return enumEntries<E>().getOrNull(ordinal + stepSize)
}

/**
 * Returns the enum entry [stepSize] before this enum entry, in declaration order.
 * Cycles back to the last entry if the beginning is reached.
 */
inline fun <reified E : Enum<E>> E.previous(stepSize: Int = 1): E {
    val entries = enumEntries<E>()
    return entries[(ordinal - stepSize).mod(entries.size)]
}

/**
 * Returns the enum entry [stepSize] before this enum entry, in declaration order.
 * Returns `null` if the beginning is reached.
 */
inline fun <reified E : Enum<E>> E.previousOrNull(stepSize: Int = 1): E? {
    return enumEntries<E>().getOrNull(ordinal - stepSize)
}

/**
 * Returns the enum by matching ordinals position, or the given default value if no ordinals match.
 *
 * @param ordinal The ordinal value in this enum
 * @param defaultValue The default value to return if the ordinal is out of range
 *
 * @return The enum value matching the ordinal, or the default.
 *
 */
inline fun <reified T : Enum<T>> Enum.Companion.byOrdinalOrDefault(ordinal: Int, defaultValue: T): T =
    if (ordinal !in enumValues<T>().indices) defaultValue else enumValues<T>()[ordinal]


/** Returns the first enum entry matched by the specified [filter], or `null` otherwise. */
 inline fun <reified E : Enum<E>> Enum.Companion.firstEnumValueOfOrNull(filter: (E) -> Boolean): E? =
    enumValues<E>().firstOrNull(filter)

/** Returns the first enum entry of which the specified [property] is matched by the specified [filter], or `null` otherwise. */
 inline fun <reified E : Enum<E>, V> Enum.Companion.firstEnumValueOfOrNull(property: KProperty1<in E, V>, filter: (V) -> Boolean): E? =
    enumValues<E>().firstOrNull { filter(property.get(it)) }

/** Returns the first enum entry of which the specified [property] matches the specified [probe], or `null` otherwise. */
 inline fun <reified E : Enum<E>, V> Enum.Companion.firstEnumValueOfOrNull(property: KProperty1<in E, V>, probe: V): E? =
    enumValues<E>().firstOrNull { property.get(it) == probe }

/** Returns the first enum entry matched by the specified [filter], or throws a [NoSuchElementException] otherwise. */
 inline fun <reified E : Enum<E>> Enum.Companion.firstEnumValueOf(filter: (E) -> Boolean): E =
    firstEnumValueOfOrNull(filter)
        ?: throw NoSuchElementException("${E::class.simpleName} contains no value matching the predicate.")

/** Returns the first enum entry of which the specified [property] is matched by the specified [filter], or throws a [NoSuchElementException] otherwise. */
 inline fun <reified E : Enum<E>, V> Enum.Companion.firstEnumValueOf(property: KProperty1<in E, V>, filter: (V) -> Boolean): E =
    firstEnumValueOfOrNull(property, filter)
        ?: throw NoSuchElementException("${E::class.simpleName} contains no value of which the property ${property.name.quoted} matches the predicate.")

/** Returns the first enum entry of which the specified [property] matches the specified [probe], or throws a [NoSuchElementException] otherwise. */
 inline fun <reified E : Enum<E>, V> Enum.Companion.firstEnumValueOf(property: KProperty1<in E, V>, probe: V): E =
    firstEnumValueOfOrNull(property, probe)
        ?: throw NoSuchElementException("${E::class.simpleName} contains no value of which the property ${property.name.quoted} is ${probe.toString().quoted}.")


/**
 * The enum constant [E] with the [Enum.ordinal]
 * being this constant's ordinal -1.
 *
 * If this enum constant is the first constant,
 * the last one is returned.
 */
 inline val <reified E : Enum<E>> E.predecessor: E
    get() {
        val enumValues = enumValues<E>()
        return enumValues[(ordinal - 1).mod(enumValues.size)]
    }

/**
 * The enum constant [E] with the [Enum.ordinal]
 * being this constant's ordinal +1.
 *
 * If this enum constant is the last constant,
 * the first one is returned.
 */
 inline val <reified E : Enum<E>> E.successor: E
    get() {
        val enumValues = enumValues<E>()
        return enumValues[(ordinal + 1).mod(enumValues.size)]
    }

/**
 * Retrieves an enum constant by its name, optionally ignoring case.
 *
 * @param name The name of the enum constant.
 * @param ignoreCase Whether to ignore a case when matching the name.
 * @return The enum constant of type `T`.
 * @throws IllegalArgumentException If no matching enum constant is found.
 */
inline fun <reified T : Enum<T>> enumValueOf(name: String, ignoreCase: Boolean = false): T {
    return if (ignoreCase) {
        enumValues<T>().find { it.name.equals(name, ignoreCase = true) }
            ?: throw IllegalArgumentException("No enum constant ${T::class}.$name")
    } else {
        kotlin.enumValueOf(name)
    }
}


@Suppress("Detekt.TooGenericExceptionCaught")
inline fun <reified T : Enum<T>> safeEnumOf(input: String?, default: T): T {
    return when (input) {
        null -> default

        else -> try {
            enumValueOf(input)
        } catch (e: RuntimeException) {
            default
        }
    }
}

@Suppress("Detekt.TooGenericExceptionCaught")
inline fun <reified T : Enum<T>> safeEnumOrNull(input: String?): T? {
    return when (input) {
        null -> null

        else -> try {
            enumValueOf(input) as T
        } catch (e: RuntimeException) {
            null
        }
    }
}

inline fun <reified T : Enum<T>> safeEnumsOf(input: String?, delimiter: String = ","): List<T> {
    return when (input) {
        null -> emptyList()
        else -> input.splitAndTrimToSet(delimiter).mapNotNull { safeEnumOrNull<T>(it) }
    }
}

inline fun <reified T : Enum<T>> T.prettyName(): String = name.lowercase().replaceFirstChar { it.uppercase() }

inline fun <reified T : Enum<T>> prettyNames(): List<String> = enumValues<T>().map { it.prettyName() }
